﻿// ===================================
// <copyright>LogoUI Co.</copyright>
// <author>Vlad Spivak</author>
// <email>mailto:vlads@logoui.co.il</email>
// <created>20/00/10</created>
// <lastedit>Saturday, November 20, 2010</lastedit>

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the 'Software'), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//
// <remarks>Part of this software based on various internet sources, mostly on the works
// of members of Wpf Disciples group http://wpfdisciples.wordpress.com/
// Also project may contain code from works of Nito and OpenLight group</remarks>
// ====================================================================================//


using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
using System.Globalization;

namespace LogoFX.Controls
{
    /// <summary>
    /// Displays a busy animation.
    /// </summary>
    [TemplatePart(Name = "part1", Type = typeof(FrameworkElement))]
    [TemplatePart(Name = "part2", Type = typeof(FrameworkElement))]
    [TemplatePart(Name = "part3", Type = typeof(FrameworkElement))]
    [TemplatePart(Name = "part4", Type = typeof(FrameworkElement))]
    [TemplatePart(Name = "part5", Type = typeof(FrameworkElement))]
    [TemplatePart(Name = "part6", Type = typeof(FrameworkElement))]
    [TemplatePart(Name = "part7", Type = typeof(FrameworkElement))]
    [TemplatePart(Name = "part8", Type = typeof(FrameworkElement))]
    [TemplatePart(Name = "part_container", Type = typeof(FrameworkElement))]
    [TemplateVisualState(Name = "normal", GroupName = "CommonStates")]
    [TemplateVisualState(Name = "state1", GroupName = "CommonStates")]
    [TemplateVisualState(Name = "state2", GroupName = "CommonStates")]
    [TemplateVisualState(Name = "state3", GroupName = "CommonStates")]
    [TemplateVisualState(Name = "state4", GroupName = "CommonStates")]
    [TemplateVisualState(Name = "state5", GroupName = "CommonStates")]
    [TemplateVisualState(Name = "state6", GroupName = "CommonStates")]
    [TemplateVisualState(Name = "state7", GroupName = "CommonStates")]
    [TemplateVisualState(Name = "state8", GroupName = "CommonStates")]
    public class BusyAnimation : ContentControl
    {
        #region Constants

        private const int NUM_STATES = 8;

        #endregion

        #region Dependency properties


        /// <summary>
        /// StepInterval property to control speed of animation.
        /// </summary>
        public static readonly DependencyProperty StepIntervalProperty = DependencyProperty.Register(
          "StepInterval",
          typeof(int),
          typeof(BusyAnimation),
          new PropertyMetadata((o, e) => ((BusyAnimation)o).StepInterval = (int)e.NewValue));

        /// <summary>
        /// IsRunning property to control whether the 
        /// animation is running.
        /// </summary>
        public static readonly DependencyProperty IsRunningProperty = DependencyProperty.Register(
          "IsRunning",
          typeof(object),
          typeof(BusyAnimation),
          new PropertyMetadata((o, e) => ((BusyAnimation)o).SetupRunningState(e.NewValue)));

        #endregion

        #region Member fields and properties

        private DispatcherTimer _timer;
        private int _state = -1;

        /// <summary>
        /// Gets or sets a property controlling
        /// the speed of the animation.
        /// </summary>
        public int StepInterval
        {
            get { return (int)GetValue(StepIntervalProperty); }
            set
            {
                SetValue(StepIntervalProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets a property controlling
        /// whether the animation is running.
        /// </summary>
        public object IsRunning
        {
            get
            {
                object val = GetValue(IsRunningProperty);
                return (val != null ?
                  (bool)Convert.ChangeType(val, typeof(bool), CultureInfo.InvariantCulture) :
                  false);
            }
            set
            {
                bool val = (bool)Convert.ChangeType(value, typeof(bool), CultureInfo.InvariantCulture);
                SetValue(IsRunningProperty, val);
            }
        }

        private void SetupRunningState(object isRunning)
        {
            bool val = (bool)Convert.ChangeType(isRunning, typeof(bool), CultureInfo.InvariantCulture);
            if (val)
                StartTimer();
            else
                StopTimer();

            GoToState(true);
        }

        #endregion

        #region Constructor

        /// <summary>
        /// Creates an instance of the control.
        /// </summary>
        public BusyAnimation()
        {
            DefaultStyleKey = typeof(BusyAnimation);
            Loaded += (o, e) =>
            {
                ArrangeParts();
                GoToState(true);
            };
            SizeChanged += new SizeChangedEventHandler(BusyAnimation_SizeChanged);
            LayoutUpdated += new EventHandler(BusyAnimation_LayoutUpdated);
        }

        #endregion

        #region Timer

        private void StartTimer()
        {
            StopTimer();
            _timer = new DispatcherTimer();
            _timer.Interval = TimeSpan.FromMilliseconds(StepInterval);
            _timer.Tick += new EventHandler(timer_Tick);
            _timer.Start();
        }

        private void StopTimer()
        {
            if (_timer != null)
            {
                _timer.Stop();
                _timer.Tick -= new EventHandler(timer_Tick);
                _timer = null;
            }
        }

        void timer_Tick(object sender, EventArgs e)
        {
            _state++;
            if (_state >= NUM_STATES)
                _state = 0;

            GoToState(true);
        }

        #endregion

        #region State

        private void GoToState(bool useTransitions)
        {
            if ((bool)IsRunning)
            {
                bool result = VisualStateManager.GoToState(this, string.Format("state{0}", _state + 1), useTransitions);
            }
            else
                VisualStateManager.GoToState(this, "normal", useTransitions);
        }

        #endregion

        #region Parts

        void BusyAnimation_LayoutUpdated(object sender, EventArgs e)
        {
            ArrangeParts();
        }

        void BusyAnimation_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            ArrangeParts();
        }

        private void ArrangeParts()
        {
            if (!(bool)IsRunning)
            {
                return;
            }
            FrameworkElement fm = (FrameworkElement)FindChild(this, "part_container");
            if(fm == null)
                return;

            double width = fm.ActualWidth;
            double height = fm.ActualHeight;
            double scale = Math.Min(width, height);
            double theta = (2.0 * Math.PI) / NUM_STATES;

            for (int n = 0; n < NUM_STATES; n++)
            {
                FrameworkElement item = (FrameworkElement)FindChild(this, "part" + (n + 1).ToString());
                if (item != null)
                {
                    double itemTheta = theta * (double)n;

                    double itemScale = scale / 4.0;
                    double radius = (scale - itemScale) / 2.0;

                    double x = (radius * Math.Cos(itemTheta)) - (itemScale / 2) + (width / 2);
                    double y = (radius * Math.Sin(itemTheta)) - (itemScale / 2) + (height / 2);

                    item.Width = itemScale;
                    item.Height = itemScale;
                    item.SetValue(Canvas.LeftProperty, x);
                    item.SetValue(Canvas.TopProperty, y);
                }
            }
        }

        #endregion

        #region Helpers

        private DependencyObject FindChild(DependencyObject parent, string name)
        {
            DependencyObject found = null;
            int count = VisualTreeHelper.GetChildrenCount(parent);
            for (int x = 0; x < count; x++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(parent, x);
                string childName = child.GetValue(FrameworkElement.NameProperty) as string;
                if (childName == name)
                {
                    found = child;
                    break;
                }
                else found = FindChild(child, name);
            }

            return found;
        }

        #endregion
    }
}
